/*
author :qianbo
email  :418511899@qq.com
听雨堂 丁香结
address https://gitee.com/guanzhi0319/flvmux/
*/


#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <mutex>
using namespace std;

enum {
	FLV_TAG_TYPE_AUDIO = 0x08,
	FLV_TAG_TYPE_VIDEO = 0x09,
	FLV_TAG_TYPE_META = 0x12,
};
#define FLV_TAG_HEAD_LEN 11
#define FLV_PRE_TAG_LEN 4

//#ifndef _WIN32
//struct FLVTag {
//	uint8_t type;
//	uint8_t data_size[3];
//	uint8_t timestamp[3];
//	uint8_t timestamp_ex;
//	uint8_t streamid[3];
//} __attribute__((__packed__));
//#else
//#pragma   pack(push,1)
//typedef struct FLVTag {
//	uint8_t type;
//	uint8_t data_size[3];
//	uint8_t timestamp[3];
//	uint8_t timestamp_ex;
//	uint8_t streamid[3];
//}FLVTag;
//#pragma   pack(pop)
//#endif
typedef struct PutBitContext
{
	unsigned int bit_buf;
	int bit_left;
	char *buf, *buf_ptr, *buf_end;
	int size_in_bits;
} PutBitContext;

static inline void init_put_bits(PutBitContext *s, char *buffer, int buffer_size)
{
	if (buffer_size < 0) {
		buffer_size = 0;
		buffer = 0;
	}

	s->size_in_bits = 8 * buffer_size;
	s->buf = buffer;
	s->buf_end = s->buf + buffer_size;
	s->buf_ptr = s->buf;
	s->bit_left = 32;
	s->bit_buf = 0;
}

static inline void put_bits(PutBitContext *s, int n, unsigned int value)
{
	unsigned int bit_buf;
	int bit_left;

	bit_buf = s->bit_buf;
	bit_left = s->bit_left;

	if (n < bit_left) {
		bit_buf = (bit_buf << n) | value;
		bit_left -= n;
	}
	else {
		bit_buf <<= bit_left;
		bit_buf |= value >> (n - bit_left);
		UI32ToBytes((uint8_t*)s->buf_ptr, bit_buf);
		//AV_WB32(s->buf_ptr, bit_buf);
		//printf("bitbuf = %08x\n", bit_buf);
		s->buf_ptr += 4;
		bit_left += 32 - n;
		bit_buf = value;
	}

	s->bit_buf = bit_buf;
	s->bit_left = bit_left;
}
static inline void flush_put_bits(PutBitContext *s)
{
	s->bit_buf <<= s->bit_left;
	while (s->bit_left < 32) {
		*s->buf_ptr++ = s->bit_buf >> 24;
		s->bit_buf <<= 8;
		s->bit_left += 8;
	}
	s->bit_left = 32;
	s->bit_buf = 0;
}

inline char* DoubleToBytes(char* buf, double val)
{
	union {
		unsigned char dc[8];
		double dd;
	} d;
	unsigned char b[8];

	d.dd = val;

	b[0] = d.dc[7];
	b[1] = d.dc[6];
	b[2] = d.dc[5];
	b[3] = d.dc[4];
	b[4] = d.dc[3];
	b[5] = d.dc[2];
	b[6] = d.dc[1];
	b[7] = d.dc[0];
	memcpy(buf, b, 8);
	return buf + 8;
}

inline uint8_t* UI08ToBytes(uint8_t* buf, unsigned char val)
{
	buf[0] = (uint8_t)(val) & 0xff;
	return buf + 1;
}

inline uint8_t* UI16ToBytes(uint8_t* buf, unsigned short val)
{
	buf[0] = (uint8_t)(val >> 8) & 0xff;
	buf[1] = (uint8_t)(val) & 0xff;
	return buf + 2;
}
inline uint8_t* UI24ToBytes(uint8_t* buf, unsigned int val)
{
	buf[0] = (uint8_t)(val >> 16) & 0xff;
	buf[1] = (uint8_t)(val >> 8) & 0xff;
	buf[2] = (uint8_t)(val) & 0xff;
	return buf + 3;
}
inline uint8_t* UI32ToBytes(uint8_t* buf, unsigned int val)
{
	buf[0] = (uint8_t)(val >> 24) & 0xff;
	buf[1] = (uint8_t)(val >> 16) & 0xff;
	buf[2] = (uint8_t)(val >> 8) & 0xff;
	buf[3] = (uint8_t)(val) & 0xff;
	return buf + 4;
}


typedef enum enum_av{
	enum_av_a = 0x04,
	enum_av_v = 0x01,
	enum_av_av = 0x05
}enum_av;
typedef struct s_av_config
{
	uint8_t *sps = NULL;
	int len_sps = 0;
	uint8_t* pps = NULL;
	int len_pps = 0; 
	enum_av flag_av;
	int samplerate = 44100;
	int channel = 2;
}s_av_config;


class c_flv_mux
{
private:
	std::mutex  m_mutex;
	uint8_t * m_meta_data = NULL;
	//9 bytes header + 4 bytes pretag length = 0
	char * m_header = "FLV\x1\x5\0\0\0\x9\0\0\0\0";
public:

	class Lock {
	private:
		std::unique_lock<std::mutex> _lock;
	public:
		inline Lock(c_flv_mux* parent) : _lock(parent->m_mutex) {}
	};
	uint8_t* flv_header(uint8_t* header, bool is_have_audio, bool is_have_video)
	{
		if (is_have_audio && is_have_video)
			header[4] = 0x05;
		else if (is_have_audio && !is_have_video)
			header[4] = 0x04;
		else if (!is_have_audio && is_have_video)
			header[4] = 0x01;
		else
			header[4] = 0x00;
		return header + 13;
	}

	uint8_t * connect_meta(const char * connect_url, int len_conurl, 
		const char * addtion ,int len_add)
	{
		// /live/userid
		//���ֽ�pretag size 11�ֽ�ͷ�� + 
		int len_data = len_conurl + len_add;
		//�����ո�
		int len_frame = 11 + 2 + len_data;

		//��ȥ11���ֽڵ�ͷ��
		int len_body =  len_data;
		int len_data = len_conurl + len_add;
		uint8_t * meta_data = new uint8_t[len_frame + 4];

		//
	}


protected:	
	
	void flv_write_11_header(
		uint8_t *buf, uint32_t len_body,
		uint32_t timestamp, uint8_t tag_type)
	{
		uint8_t* pos = buf;
		//flvtag 11�ֽ�
		*pos++= tag_type;//  0x09-> video | 0x08-> audio
		*pos++ = (uint8_t)(len_body >> 16); //data len
		*pos++ = (uint8_t)(len_body >> 8); //data len
		*pos++ = (uint8_t)(len_body); //data len
		*pos++ = (uint8_t)(timestamp >> 16); //time stamp
		*pos++ = (uint8_t)(timestamp >> 8); //time stamp
		*pos++ = (uint8_t)(timestamp); //time stamp
		*pos++ = (uint8_t)(timestamp >> 24); //time stamp
		*pos++ = 0x00; //stream id 0
		*pos++ = 0x00; //stream id 0
		*pos   = 0x00; //stream id 0
		return;
	}

	void flv_header_v_tag(
		uint8_t *buf,
		const uint8_t *sps, uint32_t len_sps,
		const uint8_t *pps, uint32_t len_pps)
	{
		int len_data = len_sps + len_pps;
		int len_frame = 11 + 16 + len_data;

		//��ȥ11���ֽڵ�ͷ��
		int len_body = 16 + len_data;

		flv_write_11_header(buf, len_body, 0, FLV_TAG_TYPE_VIDEO);
		//buf's length  11 + len_sps + len_pps + 16 bytes + 4bytes (last pretag lenth)
		//int bodylen = 11 + sps_len + pps_len + 16;
		//uint8_t *buf = (uint8_t *)malloc(bodylen + 4);

		//����11���ֽ�
		uint8_t * pos = buf + 11;
		//flv VideoTagHeader
		*pos++ = 0x17; //key frame, AVC
		*pos++ = 0x00; //avc sequence header
		*pos++ = 0x00; //composit time 
		*pos++ = 0x00; //composit time
		*pos++ = 0x00; //composit time
		*pos++ = 0x01;   //configurationversion
		*pos++ = sps[1]; //avcprofileindication
		*pos++ = sps[2]; //profilecompatibilty
		*pos++ = sps[3]; //avclevelindication
		*pos++ = 0xff;   //reserved + lengthsizeminusone
		*pos++ = 0xe1;   //numofsequenceset
		*pos++ = (uint8_t)(len_sps >> 8); //sequence parameter set length high 8 bits
		*pos++ = (uint8_t)(len_sps); //sequence parameter set  length low 8 bits
		memcpy(pos, sps, len_sps); //H264 sequence parameter set
		pos += len_sps;
		*pos++ = 0x01; //numofpictureset
		*pos++ = (uint8_t)(len_pps >> 8); //picture parameter set length high 8 bits
		*pos++ = (uint8_t)(len_pps); //picture parameter set length low 8 bits
		memcpy(pos, pps, len_pps); //H264 picture parameter set
		pos += len_pps;
		*pos++ = (uint8_t)(len_frame >> 24); //data len
		*pos++ = (uint8_t)(len_frame >> 16); //data len
		*pos++ = (uint8_t)(len_frame >> 8); //data len
		*pos = (uint8_t)(len_frame); //data len

	}

	void flv_header_a_tag(uint8_t * buf,
		int sample_rate, int channels)
	{
		//11�ֽڵ�tag ����Ƶ4�ֽ�ͷ����4�ֽ�pretag����

		int len_data = 4;
		int len_frame = 11 + 4;

		//��ȥ11���ֽڵ�ͷ��
		int len_body = len_data;

		flv_write_11_header(buf, len_body, 0, FLV_TAG_TYPE_AUDIO);
		uint8_t *pbuf = buf + 11;
		/* SoundFormat|SoundRate|SoundSize|SoundType:0xa0|0x0c|0x02|0x01*/
		pbuf = UI08ToBytes(pbuf, 0xaf);

		//unsigned char flag = 0;
		//flag = (10 << 4) |  // soundformat "10 == AAC"
		//	(3 << 2) |      // soundrate   "3  == 44-kHZ"
		//					//(0<<1) |
		//	(1 << 1) |      // soundsize   "1  == 16bit"
		//	1;              // soundtype   "1  == Stereo" 


		pbuf = UI08ToBytes(pbuf, 0); // AACPacketType: 0x00 - AAC sequence header

		int put_value = 0;
		switch (sample_rate)
		{
		case 44100:
			put_value = 0x04;
			break;
		case 16000:
			put_value = 0x08;
			break;
		case 64000:
			put_value = 0x02;
			break;
		case 22050:
			put_value = 0x07;
			break;
		case 8000:
			put_value = 0x0b;
			break;
		}

		//  0: 96000 Hz		//	1 : 88200 Hz		//	2 : 64000 Hz
		//	3 : 48000 Hz	//	4 : 44100 Hz		//	5 : 32000 Hz
		//	6 : 24000 Hz	//	7 : 22050 Hz		//	8 : 16000 Hz
		//	9 : 12000 Hz	//	10 : 11025 Hz		//	11 : 8000 Hz
		//	12 : 7350 Hz	//	13 : Reserved		//	14 : Reserved
		//	15 : frequency is written explictly
		PutBitContext pb;
		init_put_bits(&pb, (char*)pbuf, 16);
		put_bits(&pb, 5, 2);    //object type - AAC-LC

        //44100HZ 0x1210 --- 16000HZ 0x1410 --- 8000HZ 0x1590 --- 22050HZ 0x1390
		put_bits(&pb, 4, put_value); //sample rate index, 44100 or 16000 or other
									
		put_bits(&pb, 4, 2);    //channel configuration
								//GASpecificConfig
		put_bits(&pb, 1, 0);    //frame length - 1024 samples
		put_bits(&pb, 1, 0);    //does not depend on core coder
		put_bits(&pb, 1, 0);    //is not extension

		flush_put_bits(&pb);
		pbuf += 2;
		UI32ToBytes(pbuf, len_frame);

	}

#define Pre_Tag_Len 4
	//���ذ�����
	inline int flv_v_len(int dlen)
	{
		int bodylen = 11 + 5 + dlen;
		return bodylen + Pre_Tag_Len;
	}
	inline int flv_a_len(int dlen)
	{
		int bodylen = 11 + 2 + dlen;
		return bodylen + Pre_Tag_Len;
	}
	//��ȡ��Ƶ����
	int flv_header_v_tag_len(int spslen, int ppslen)
	{
		int bodylen = 11 + spslen + ppslen + 16;
		return bodylen + Pre_Tag_Len;
	}
	int flv_header_a_tag_len()
	{
		int bodylen = 11 + 4;
		return bodylen + Pre_Tag_Len;
	}


public:
	int InitMeta(s_av_config *savconfig)
	{
		//_meta_data = new char[]
		if (savconfig == NULL)
			return -1;
		int len_total = 0;
		int len_video = 0;
		int len_audio = 0;
		if (savconfig->flag_av & enum_av_v)
			len_video = flv_header_v_tag_len(savconfig->len_sps, savconfig->len_pps);
		if (savconfig->flag_av & enum_av_a)
			len_audio = flv_header_a_tag_len();
		len_total = 13 + len_video + len_audio;
		if (len_total == 0)
			return -1;

		m_header[4] = savconfig->flag_av;

		m_meta_data = new uint8_t[len_total];
		memcpy(m_meta_data, m_header, 13);
		uint8_t * pos = &m_meta_data[0] + 13;
		if (savconfig->flag_av & enum_av_v)
		{
			flv_header_v_tag(pos,
				savconfig->sps, savconfig->len_sps,
				savconfig->pps, savconfig->len_pps);
			pos += len_video;
		}
		if (savconfig->flag_av & enum_av_a)
		{
			flv_header_a_tag(pos, savconfig->samplerate, savconfig->channel);
		}

	}


	/*
	  bodylen = 11 + 5 + dlen
	  д��buf��
	*/
	int flv_data_v(uint8_t * buf ,const uint8_t *data,
		uint32_t data_len,
		uint32_t time_stamp, int is_keyframe)
	{
		int len_frame = 11 + 5 + data_len;
		
		//��ȥ11���ֽڵ�ͷ��
		int len_body = 5 + data_len ;
		//buf lenth is frame_len + 4 ;
		//д��11���ֽڵ�ͷ��
		flv_write_11_header(buf, len_body, time_stamp, FLV_TAG_TYPE_VIDEO);
		uint8_t *pbuf = buf + 11;


		uint8_t flag = 0;
		// (FrameType << 4) | CodecID, 1 - keyframe, 2 - inner frame, 7 - AVC(h264)
		if (is_keyframe)
			flag = 0x17;
		else
			flag = 0x27;

		pbuf = UI08ToBytes(pbuf, flag);

		pbuf = UI08ToBytes(pbuf, 1);// AVCPacketType: 0x00 - AVC sequence header; 0x01 - AVC NALU
		pbuf = UI24ToBytes(pbuf, 0);// composition time

		memcpy(pbuf, data, data_len);
		pbuf += data_len;

		UI32ToBytes(pbuf, len_frame);
		return flv_v_len(data_len);
	}
	int flv_data_a(uint8_t * buf,
		const uint8_t *data, uint32_t data_len,
		uint32_t time_stamp)
	{
		/* strip ADTS from frame */
		//����7���ֽ�
		if (*buf == 0xff)
		{
			data_len -= 7;
			data = data + 7;
		}
		int len_frame = 11 + 2 + data_len;

		//��ȥ11���ֽڵ�ͷ��
		int len_body = 2 + data_len;

		//uint8_t *buf = (uint8_t *)malloc(bodylen + 4);
		//д��11���ֽڵ�ͷ��
		flv_write_11_header(buf, len_body, time_stamp, FLV_TAG_TYPE_AUDIO);
		uint8_t *pbuf = buf + 11;

		/* SoundFormat|SoundRate|SoundSize|SoundType:0xa0|0x0c|0x02|0x01*/
		pbuf = UI08ToBytes(pbuf, 0xaf);
		pbuf = UI08ToBytes(pbuf, 1); // AACPacketType: 0x01 - AAC frame data

		memcpy(pbuf, data, data_len);
		//pbuf += payload_len;
		pbuf += data_len;

		UI32ToBytes(pbuf, len_frame);
		return flv_a_len(data_len);
	}

};